본문으로 건너뛰기

Promise vs Async,Await

작성일 : 2024.12.06

1. Promise의 특징​

장점:​

  • 체이닝 .then() 을 통한 연속적인 비동기 처리 가능
  • 병렬 처리 Promise.all 가 직관적
  • 에러 처리를 catch로 한번에 가능
// Promise 체이닝 예시
fetchUser()
    .then(user => fetchProfile(user.id))
    .then(profile => fetchPosts(profile.id))
    .catch(error => console.error(error));

// 병렬 처리 예시
Promise.all([
    fetchUsers(),
    fetchProducts(),
    fetchOrders()
]).then(([users, products, orders]) => {
    // 모든 데이터 처리
});

// 1. Promise 상태
const promise = new Promise((resolve, reject) => {
    // pending: 초기 상태
    // fulfilled: resolve 호출
    // rejected: reject 호출
});

// 2. Promise 메서드
Promise.resolve(value);  // 즉시 이행
Promise.reject(error);   // 즉시 거부
Promise.race([p1, p2]); // 가장 빨리 완료된 결과
Promise.allSettled([p1, p2]); // 모든 결과(성공/실패 모두)

// 3. 에러 처리 체이닝
fetchData()
    .then(data => {
        throw new Error('에러 발생');
    })
    .then(
        success => console.log('실행되지 않음'),
        error => console.log('첫 번째 에러 핸들러:', error)
    )
    .catch(error => console.log('두 번째 에러 핸들러:', error))
    .finally(() => console.log('항상 실행'));

단점:​

  • 코드가 복잡해질수록 then 체이닝이 깊어짐
  • 조건부 로직 처리가 복잡
  • 디버깅이 어려움

2. async/await의 특징​

장점:​

  • 동기 코드와 유사한 직관적인 문법
  • 코드 가독성이 높음
  • 디버깅이 용이
  • try-catch로 일반적인 에러 처리 가능
// async/await 예시
async function getData() {
    try {
        const user = await fetchUser();
        const profile = await fetchProfile(user.id);
        const posts = await fetchPosts(profile.id);
        return posts;
    } catch (error) {
        console.error(error);
    }
}

// 조건부 비동기 처리
async function conditionalFetch(condition) {
    const user = await fetchUser();
    
    if (condition) {
        const extraData = await fetchExtra();
        return { user, extraData };
    }
    
    return { user };
}

// 1. 동시성 처리
async function concurrentProcess() {
    // 병렬 실행
    const dataPromise = fetchData();
    const userPromise = fetchUser();
    
    // 순차적 처리가 필요한 경우
    const data = await dataPromise;
    const user = await userPromise;
    
    return { data, user };
}

// 2. 에러 처리 패턴
async function errorHandling() {
    try {
        const result = await riskyOperation();
    } catch (error) {
        if (error instanceof NetworkError) {
            await handleNetworkError(error);
        } else if (error instanceof ValidationError) {
            await handleValidationError(error);
        } else {
            throw error; // 처리할 수 없는 에러는 상위로 전파
        }
    }
}

// 3. 타임아웃 처리
async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        clearTimeout(timeoutId);
        return response;
    } catch (error) {
        clearTimeout(timeoutId);
        if (error.name === 'AbortError') {
            throw new Error('Request timed out');
        }
        throw error;
    }
}

단점:​

  • 병렬 처리를 위해서는 Promise.all 등을 함께 사용해야 함
  • await 키워드를 빼먹으면 의도치 않은 동작 발생
  • async 함수는 항상 Promise를 반환

3. 실제 사용 예시와 비교​

// Promise 버전
function processData() {
    return fetchData()
        .then(data => processStep1(data))
        .then(result1 => processStep2(result1))
        .then(result2 => processStep3(result2))
        .catch(error => handleError(error));
}

// async/await 버전
async function processData() {
    try {
        const data = await fetchData();
        const result1 = await processStep1(data);
        const result2 = await processStep2(result1);
        return await processStep3(result2);
    } catch (error) {
        handleError(error);
    }
}

// 병렬 처리
async function parallelProcessing() {
    try {
        const [result1, result2] = await Promise.all([
            processTask1(),
            processTask2()
        ]);
        return combineResults(result1, result2);
    } catch (error) {
        handleError(error);
    }
}
// 순차 실행 (비효율적)
async function sequentialProcessing() {
    try {
        const result1 = await processTask1();
        const result2 = await processTask2();
        return combineResults(result1, result2);
    } catch (error) {
        handleError(error);
    }
}

// 병렬 실행을 유지하면서 async/await 사용 (효율적)
async function parallelProcessing() {
    try {
        // 두 작업이 동시에 시작됨
        const task1Promise = processTask1(); // 즉시 실행 시작
        const task2Promise = processTask2(); // 즉시 실행 시작
        
        // 작업들이 이미 실행 중이며, 여기서는 결과만 기다림
        const result1 = await task1Promise;
        const result2 = await task2Promise;
        
        return combineResults(result1, result2);
    } catch (error) {
        handleError(error);
    }
}

실무 선호도​

  • 일반적인 비동기: async/await
  • 병렬 처리: Promise.all + async/await
  • 복잡한 분기 처리: async/await
  1. async/await를 주로 사용하는 이유:
  • 코드 가독성이 높음
  • 디버깅이 용이
  • try-catch로 에러 처리가 직관적
  • 동기 코드와 유사한 구조로 유지보수 용이
  1. Promise를 사용하는 경우:
  • 병렬 처리 (Promise.all)
  • 경쟁 상태 처리 (Promise.race)
  • 이미 Promise 기반으로 작성된 레거시 코드 유지보수
// 일반적인 패턴
async function getUserData(userId) {
    try {
        const user = await fetchUser(userId);
        const posts = await fetchUserPosts(userId);
        
        return { user, posts };
    } catch (error) {
        logger.error(error);
        throw new CustomError('사용자 데이터 조회 실패');
    }
}

// 병렬 처리가 필요한 경우
async function getDashboardData() {
    const [users, products, orders] = await Promise.all([
        fetchUsers(),
        fetchProducts(),
        fetchOrders()
    ]);
    
    return { users, products, orders };
}